package main

import (
	"errors"
	"fmt"
	"github.com/alexflint/go-arg"
	"github.com/howeyc/gopass"
	"github.com/kevingimbel/colog"
	"github.com/robfig/cron/v3"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"strings"
	"sync"

	//"sync"
	"time"
)

const (
	LinuxIPCommand = `ip`
	EnvGateway     = `GATEWAY`
	EnvUsername    = "NET_USERNAME"
	EnvPasswd      = "NET_PASSWD"
	EnvCron        = "CRON"
)

var Log colog.Colog

func init() {
	var logConfig = make(map[string]string)
	logConfig["TimeFormat"] = "15:04:05"
	logConfig["LogFormat"] = "[%s][%s] -- %s"
	Log = colog.NewColog(logConfig)
}

func main() {
	if len(os.Args) >= 2 && os.Args[1] == "web" {
		// web mode if 'web' subcommand are specified.
		// Then it will run a http listener, and will perform network login if it received a http request.
		ipv6, _ := getAutoIpv6()
		if os.Getenv(EnvUsername) == "" {
			fmt.Println(Log.Error("environment variable '" + EnvUsername + "' not set"))
			return
		}
		if os.Getenv(EnvPasswd) == "" {
			fmt.Println(Log.Error("environment variable '" + EnvPasswd + "' not set"))
			return
		}
		if os.Getenv(EnvGateway) == "" {
			fmt.Println(Log.Error("environment variable '" + EnvGateway + "' not set"))
			return
		} else {
			if _, err := url.Parse(os.Getenv(EnvGateway)); err != nil {
				fmt.Println(Log.Error("Bad gateway: " + err.Error()))
				return
			}
		}

		user := User{Username: os.Getenv(EnvUsername), Ipv6Addr: ipv6, Password: os.Getenv(EnvPasswd)}
		var lastPost time.Time
		var mutex = &sync.Mutex{}
		http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
			mutex.Lock()
			defer mutex.Unlock()
			if lastPost.Add(time.Second * time.Duration(10)).After(time.Now()) {
				// the last post is 10 second ago.
				fmt.Println(Log.Info("Skipped, web login succeed in 10 seconds!"))
				w.Write([]byte("Yay, login success!\n"))
				return
			}
			if err, des := user.Post(os.Getenv(EnvGateway)); err != nil {
				fmt.Println(Log.Error(des + " [web error: " + err.Error() + "]"))
				w.WriteHeader(500)
				w.Write([]byte(des + "\n"))
			} else {
				fmt.Println(Log.Success("Yay, web login success!"))
				w.Write([]byte("Yay, login success!\n"))
				lastPost = time.Now()
			}
		})
		fmt.Println(Log.Info("listening on port 8080"))
		if err := http.ListenAndServe(":8080", nil); err != nil {
			panic(err)
		}
	} else if len(os.Args) >= 2 && os.Args[1] == "cron" {
		ipv6, _ := getAutoIpv6()
		if os.Getenv(EnvUsername) == "" {
			fmt.Println(Log.Error("environment variable '" + EnvUsername + "' not set"))
			return
		}
		if os.Getenv(EnvPasswd) == "" {
			fmt.Println(Log.Error("environment variable '" + EnvPasswd + "' not set"))
			return
		}
		if os.Getenv(EnvCron) == "" {
			fmt.Println(Log.Error("environment variable '" + EnvCron + "' not set"))
			return
		}
		if os.Getenv(EnvGateway) == "" {
			fmt.Println(Log.Error("environment variable '" + EnvGateway + "' not set"))
			return
		} else {
			if _, err := url.Parse(os.Getenv(EnvGateway)); err != nil {
				fmt.Println(Log.Error("Bad gateway: " + err.Error()))
				return
			}
		}

		user := User{Username: os.Getenv(EnvUsername), Ipv6Addr: ipv6, Password: os.Getenv(EnvPasswd)}
		c := cron.New()
		c.AddFunc(os.Getenv(EnvCron), func() { // e.g. */1 * * * *
			if err, des := user.Post(os.Getenv(EnvGateway)); err != nil {
				fmt.Println(Log.Error(des + " [ error: " + err.Error() + "]"))
			} else {
				fmt.Println(Log.Success("Yay, net login success!"))
			}
		})
		c.Start()
		wg := sync.WaitGroup{}
		wg.Add(1)
		wg.Wait()
	} else {
		// in interactive mode as default.
		var args Args
		arg.MustParse(&args)

		// get ipv6 address
		if args.Ipv6Addr == "" {
			if args.AutoIpv6 {
				if ipv6, err := getAutoIpv6(); err != nil {
					fmt.Println(Log.Error("Whoops! Error while obtaining ipv6 address:" + err.Error()))
					os.Exit(2)
				} else {
					args.Ipv6Addr = ipv6
					fmt.Println(Log.Info("autoipv6=true; ipv6 address detected:" + args.Ipv6Addr))
				}
			} else { //ignore ipv6 address
				fmt.Println(Log.Info("ipv6 address was ignored"))
			}
		} else {
			fmt.Println(Log.Info("using ipv6 address: " + args.Ipv6Addr))
		}

		// get user password.
		if args.Password == "" {
			//get ipv6 address
			fmt.Print("Enter Password: ")
			bytePassword, err := gopass.GetPasswd()
			if err != nil { // error
				fmt.Println(Log.Error("Whoops! Error while parsing password:" + err.Error()))
				os.Exit(1)
			} else {
				args.Password = string(bytePassword)
			}
		}

		// send post
		user := User{Username: args.Username, Ipv6Addr: args.Ipv6Addr, Password: args.Password}
		fmt.Println(Log.Info("posting HTTP request"))

		remoteAddr := DefaultRemoteAddress
		if args.Gateway != "" {
			if _, err := url.Parse(args.Gateway); err != nil {
				fmt.Println(Log.Error("Bad gateway: " + err.Error()))
				os.Exit(1)
				return
			}
		}
		if err, des := user.Post(remoteAddr); err != nil {
			fmt.Println(Log.Error(des + " [error: " + err.Error() + "]"))
		} else {
			fmt.Println(Log.Success("Yay, login success!"))
		}
	}
}

type Args struct {
	Username string `arg:"required,-u,help:your student ID or job ID"`
	Password string `arg:"-p"`
	Gateway  string `arg:"-g,help:default='' the target gateway (e.g. http://202.204.48.66)."`
	Ipv6Addr string `arg:"-i,help:default='' login with ipv6 mode"`
	AutoIpv6 bool   `arg:"-a,help:default='false' obtaining ipv6 address automatically and login with ipv6 mode"`
}

func (Args) Description() string {
	return "tool for signing in USTB network in linux command line."
}

func (Args) Version() string {
	return "netlogin\t0.1.1\tAuthor:genshen | http://git.gensh.me/genshen/NetLogin\n"
}

// get ipv6 address from command line.
func getAutoIpv6() (string, error) {
	cmd := exec.Command(LinuxIPCommand, "-6", "addr") //ip -6 addr | sed -e's/^.*inet6 \([^ ]*\)\/.*$/\1/;t;d'
	out, err := cmd.Output()
	if err != nil {
		return "", err
	}
	lines := strings.Split(string(out), "\n")
	for _, line := range lines { //just like:   inet6 2001:da8:208:18a:224:ecff:fef0:2593/64 scope global dynamic
		temp := strings.Split(line, "/")
		if len(temp) >= 2 {
			tmp := strings.Split(strings.TrimLeft(temp[0], " "), " ")
			if len(tmp) >= 2 {
				line = tmp[1]
				if strings.HasPrefix(line, "200") {
					return line, nil
				}
			}
		}
	}
	return "", errors.New("no ipv6 address found")
}
